home *** CD-ROM | disk | FTP | other *** search
- ε Curso de programación en assembler (y III)π
-
- εEl primer programaπ
-
- Hoy, por fin, abordaremos el primer ejemplo práctico en assembler. El primer
- programa que haremos será (como no) el δ"hola mundo"π. Pero antes de poder darle
- caña al ensamblador necesitamos tener alguno... la verdad es que todos teneis
- uno en vuestro PC, el ΩDebugπ, pero cuando hay que hacer cosas complicadas se
- hace imposible y por eso a lo largo del curso pasaremos un poco de él (excepto
- en este artículo) y nos centraremos en el ΩTurbo Assemblerπ y el ΩA86π que al ser
- shareware y tener una sintaxis simplificada os ayudará a empezar.
- El Debug del DOS se invoca escribiendo δ'DEBUG'π en la linea de comandos y si
- quieres puedes poner detrás el nombre del programa a debuggear o a crear.
- Para hacer el δ"hola mundo"π entraremos escribiendo:
-
- ΓDEBUG hola.comπ
-
- lo primero que el programa nos dirá es que hemos cometido un error porque el
- fichero 'δhola.comπ' no existe (si no nos da el error es que si existe y lo
- sobre-escribiremos). Ahora nos encontramos con un prompt que es una rallita '-'
- y ahí es donde deberemos entrar los comandos, por ejemplo 'δ?π' te mostrará
- todos los posibles. El que más nos va a interesar de momento es 'δaπ' que nos
- permite ensamblar directamente las intrucciones que entremos.
- La primera instrucción que veremos hoy (y la que es más usada) será ΩMOVπ.
- Con ella conseguimos copiar el contenido de una variable, registro, constante
- en otro. Su prototipo es bastante sencillo:
-
- ΓMOV <destino>, <origen>π
-
- donde ∞<origen>π será el valor que copiaremos en ∞<destino>π. En general, en el
- ensamblador estandard de los 80X86 se especifica primero el destino y luego
- el origen como veremos en siguientes instrucciones. Un registro es una especie
- de variable que se guarda muy cerquita del procesador y por ello se accede a
- ella de forma muy rápida. Una variable la consideraremos una porción de memoria
- de tamaño δBYTEπ, δWORDπ... que podrá tomar distintos valores según lo que queramos
- hacer. Una constante será un valor que definiremos cuando creemos el código
- fuente y que no se podrá cambiar bajo ningún concepto.
- Los registros son una parte fundamental de la arquitectura de un ordenador
- que ayudan al programador a acelerar los programas. Existen 4 registros llama-
- dos "de uso general" que son δAXπ, δBXπ, δCXπ y δDXπ de 16bits cada uno y divididos en
- dos partes de 8bits (parte alta-high y parte baja-low):
-
- Γ AX --> AH - ALπ
- Γ BX --> BH - BLπ
- Γ CX --> CH - CLπ
- Γ DX --> DH - DLπ
-
- Existen tambien otros registros no importantes de momento como son δCSπ, δSSπ, δSPπ,
- δDIπ... Simplemente mencionar el registro δDSπ que es un registro de segmento que
- lo que hace es apuntar al segmento de memoria sobre el que se opera (recordad
- lo que deciamos de la memoria segmentada del PC). Por ejemplo, si decimos que
- δDS:DXπ apunta a una cadena de texto, lo que queremos decir es que en el segmento
- δDSπ si nos desplazamos δDXπ posiciones nos encontraremos con una ristra de bytes
- que definen una cadena de texto; a δDSπ se le llamaria segmento y a δDXπ se le
- llamaria offset o desplazamiento.
- Bueno veamos como queda el programa y seguimos explicando cosas a partir del
- mismo:
-
- Γ mov ax, cs [1]π
- Γ mov ds, ax [2]π
- Γ mov dx, 110 [3]π
- Γ mov ah, 9 [4]π
- Γ int 21 [5]π
- Γ mov ax, 4c00 [6]π
- Γ int 21 [7]π
- Γ db 'Hola mundo!!$' [8]π
-
- para guardarlo debemos presionar δENTERπ, escribir 'ΩRCXπ', pulsar δENTERπ, escribir
- la longitud del programa a guardar y δENTERπ y escribir 'Ωwπ' (y δENTERπ :). Si te
- dice que hay algún error es que no has hecho algo bien. En este caso el tamaño
- del programa a almacenar es δ1Dπ (habrás observado que en el Debug todos los
- números se deben escribir en hexadecimal.
- Vamos con el comentario línea por línea del programa (supongo que te darás
- cuenta de que no debes escribir los numeros entre corchetes ;). En la primera
- linea vemos una de las asignaciones δMOVπ de las que hablabamos antes, el regis-
- tro δCSπ (que apunta al trozo de código) se cópia en δAXπ. La segunda instrucción
- hace algo parecido, pero esta vez cópia en un registro de segmento lo que habia
- en δAXπ (que mira tu por donde es lo mismo que lo que hay en δCSπ). En la linea 3
- se asigna una constante a un registro, δDXπ pasará a valer φ110hπ. Lo mismo que
- antes se hace en la cuarta línea solo que ahora se trata de un registro de
- 8bits (la mitad de arriba de δAXπ). Las instrucciones 5 y 7 son llamadas a
- interrupciones y las veremos ahora enseguida. La instrucción 6 es otra vez una
- asignación de una constante a un registro (observa lo frecuentes que son las
- instrucciones δMOVπ). Y por último, la palabra clave 'δdbπ' no se trata de una
- instrucción própiamente dicha, sino que sirve para definir una espécie de
- matriz o array de δBYTESπ (como en los lenguajes de alto nivel).
- Todo esto está muy bien, ¿pero donde está el análisis semántico? Pues aquí
- mismo... Si miramos lo que hace el programa en las 2 primeras líneas es meter
- en 'δdsπ' el contenido de 'δcsπ', entonces ¿por que no hacer 'δMOV DS,CSπ' directa-
- mente? Sencillo, porque no se puede: los registros de segmento no pueden
- asignarse a otros registros de segmento, por tanto, cada vez que queramos
- hacer algo así, tendremos que copiar a un registro auxiliar el contenido del
- origen. Las dos siguientes líneas (3 y 4) son una simple preparación para la
- interrupción que se lanzará en la línea 5 (tambien forman parte de esa
- "δpreparaciónπ" las instrucciones 1 y 2). Las interrupciones las veremos más
- a fondo ahora mismo, pero baste decir que la interrupción que llamamos primero
- nos permite ver una cadena en pantalla y la segunda hace que regresemos al
- sistema operativo.
-
- ε Las interrupcionesπ
-
- Las interrupciones son un método de simplificar la tarea al programador ya
- son como pequeñas funciones o procedimientos que devuelven valores valiosos o
- simplemente hacen algo que resultaria difícil de programar manualmente. Los
- "parámetros" se le pasan en los registros y los valores nos los devuelve en
- registros tambien. En el PC hay exactamente δ256π servicios de interrupción
- (del 00h al FFh) que se dividen en centenares de subservicios por lo que para
- programar en assembler se hace necesaria una lista suficientemente completa de
- todas las interrupciones como la de ΩRalf Brownπ (que podeis encontrar en muchos
- CD-ROMs de share).
- Si comprendemos apróximadamente este funcionamiento ya estamos en disposición
- de ver que es exactamente lo que se hacia en el programa de ejemplo. La δINT 21hπ
- es la interrupción que lleva el grueso de los servicios del sistema operativo
- (DOS), tiene unos 200 subservicios y debe identificar a cual de ellos se va
- a llamar en cada momento. Para ello, en el registro δAHπ debemos guardar el
- número de subservicio (en nuestro caso el 09) y llamar a los servicios con
- los parámetros correspondientes. La lista de Ralf Brown para este caso indica
- lo siguiente:
-
- Θ--------D-2109-------------------------------π
- ΘINT 21 - DOS 1+ - WRITE STRING TO STANDARD OUTPUTπ
- Θ AH = 09hπ
- Θ DS:DX -> '$'-terminated stringπ
- ΘReturn: AL = 24h (the '$' terminating the string, despite official docs whichπ
- Θ state that nothing is returned) (at least DOS 3.3-5.0)π
- ΘNotes: ^C/^Break are checked, and INT 23 is called if either pressedπ
- Θ standard output is always the screen under DOS 1.x, but may beπ
- Θ redirected under DOS 2+π
- Θ under the FlashTek X-32 DOS extender, the pointer is in DS:EDXπ
- ΘSeeAlso: AH=02h,AH=06h"OUTPUT"π
-
- Lo primero que se indica es el número, servicio y nombre de la interrupción
- para poder localizarla. Luego están los parámetros de entrada que en nuestro
- caso son δAH=09hπ (lógicamente!!) y δDS:DXπ=<Cadena terminada con '$'>. Observa el
- código fuente del programa y verás como comienzas a ligar cabos sueltos. Según
- esto, en δALπ devolverá el valor φ24hπ que corresponde al valor ASCII de 'δ$π' (hace
- tambien una observación de que esto no está documentado). Como observaciones
- nos dice que pulsando CTRL-C o CTRL-PAUSA haremos saltar la interrupción 23h
- (esto lo veremos en un ejemplo de otro número) que hace que se interrumpa el
- programa. Tambien nos dice que es conveniente ver los subservicios 02h y 06h.
- Veamos nuestro programa como asigna a δDS:DXπ la cadena terminada en $ que
- aparece al final del código: primero, como ya habiamos visto, cópia el segmento
- de código en DS y luego entra el valor constante φ110hπ en δDXπ. Si en el Debug
- escribimos:
-
- Γ u 100π
-
- veremos lo que hemos introducido y podremos observar que al principio de cada
- línea aparece una cosa así:
-
- Γ XXXX:YYYYπ
-
- eso no es ni más ni menos que la dirección segmentada donde estamos guardando
- el código del programa; XXXX es el segmento e YYYY es el desplazamiento u
- offset dentro de ese segmento. Por tanto, la dirección que yo he de pasar es
- el segmento donde reside la cadena (que resulta ser el del código) y el offset
- dentro de ese segmento que es φ110hπ y que si miras el programa desensamblado
- (despues de usar el comando 'δuπ' del debug) es φ0110π precisamente.
- Un posible pero a todo esto es: pero, ¿porque has entrado el offset como
- constante y el segmento lo has sacado del registro CS? La respuesta a esto
- la entenderás mejor cuando veamos los programas COM y EXE, aunque es sencilla
- (a priori); cuando el sistema operativo carga un programa (COM en este caso)
- lo que hace es buscar un segmento de memoria libre (los TSR ocupan memoria y
- el própio SO) y lo carga a partir de la dirección φ0100hπ, por tanto podemos
- saber el offset pero no el segmento donde se cargará.
-
- εEl Debugπ
-
- A parte de ensamblar y desensamblar programas, el debug (y esa es su razón
- de ser) se usa para ejecutar programas paso por paso como se hace en los
- IDEs de los lenguajes de alto nivel. Para ejecutar un paso (step) de un trozo
- de código usaremos 'p' y para trazar usaremos 'δtπ'. La diferencia entre una
- instrucción y otra es simplemente que con 'δtπ' si encontramos una INT (o algunas
- instrucciones que veremos para hacer llamadas a subprocedimientos) entraremos
- dentro de ella, mientras que con 'p' ejecutaremos el código linealmente. Para
- lo que sabemos ahora es suficiente con 'δpπ' ∞(no usar 't' delante de INT!!)π.
- La utilidad de todo esto cuando se está comenzando es increible, así podrás
- ver como las instrucciones e interrupciones modifican los registros. Hablando
- de registros, el registro IP es un registro que apunta a la instrucción que
- está siendo ejecutada en cada momento dentro del segmento δCSπ del que ya sabia-
- mos algo. Vamos a ver un ejemplo de trazeado de un programa (el "hola mundo")
- y observaremos como cambian los registros:
-
- ΓAX=109FBX=0000CX=001DDX=0000SP=FFFEBP=0000SI=0000DI=0000π
- ΓDS=109FES=109FSS=109FCS=109FIP=0102 NV UP EI PL NZ NA PO NC π
- Γ109F:0102 8ED8 MOV DS,AXπ
-
- Al ejecutarse la primera instrucción AX ha adquirido el valor de CS. Podemos
- observar tambien que DS es igual a CS y que no es necesario copiarlo, pero
- siempre es mejor asegurarse de hacer las cosas bien. La instrucción 'p' nos
- muestra tambien cual será la siguiente instrucción a ejecutar (MOV DS,AX).
- Observa el valor de CS:IP y la dirección de la siguiente instrucción a ser
- ejecutada y fíjate que es el mismo.
-
- ΓAX=109FBX=0000CX=001DDX=0000SP=FFFEBP=0000SI=0000DI=0000π
- ΓDS=109FES=109FSS=109FCS=109FIP=0104 NV UP EI PL NZ NA PO NCπ
- Γ109F:0104 BA1001 MOV DX,0110π
-
- Trás esta instrucción no podemos observar nada nuevo en DS ya que el valor era
- el mismo que tenia anteriormente. Lo que si podemos ver es que CS:IP sigue
- apuntando a la siguiente instrucción a ejecutar y esto será así siempre.
-
- ΓAX=109FBX=0000CX=001DDX=0110SP=FFFEBP=0000SI=0000DI=0000π
- ΓDS=109FES=109FSS=109FCS=109FIP=0107 NV UP EI PL NZ NA PO NC π
- Γ109F:0107 B409 MOV AH,09π
-
- Podemos ver como ha cambiado DX, pasando del valor 0000h al 0110h que es el
- offset de la cadena.
-
- ΓAX=099FBX=0000CX=001DDX=0110SP=FFFEBP=0000SI=0000DI=0000π
- ΓDS=109FES=109FSS=109FCS=109FIP=0109 NV UP EI PL NZ NA PO NC π
- Γ109F:0109 CD21 INT 21π
-
- Observa la parte alta de AX que ha pasado a valer 09h. Por si todavia no tenias
- claro eso de la parte alta y la baja mira el valor que tenia AX antes (AX=109F)
- y mira el que tiene ahora (δAX=099Fπ) lo que ha cambiado es la parte de mayor
- peso del registro. Si hubiesemos actuado sobre AL en lugar de AH, el 9Fh de AX
- hubiese pasado a 09h.
-
- ΓHola mundo!!π
- ΓAX=0924BX=0000CX=001DDX=0110SP=FFFEBP=0000SI=0000DI=0000 π
- ΓDS=109FES=109FSS=109FCS=109FIP=010B NV UP EI PL NZ NA PO NC π
- Γ109F:010B B8004C MOV AX,4C00π
-
- En este paso se ejecuta la interrupción y por tanto se muestra el mensaje en
- pantalla. Como se decia en la lista de Ralf Brown, AL tiene ahora el valor
- 24h y los demás registros permanecen inalterados (a excepción de IP, claro!!).
-
- ΓAX=4C00BX=0000CX=001DDX=0110SP=FFFEBP=0000SI=0000DI=0000π
- ΓDS=109FES=109FSS=109FCS=109FIP=010E NV UP EI PL NZ NA PO NC π
- Γ109F:010E CD21 INT 21π
-
- Vemos como AX ha adquirido el valor 4C00h para llamar a la interrupción 21h.
- Este subservicio es el que nos permite regresar al prompt del DOS pasandole
- en AH el valor 4Ch (que indica el número de subservicio) y en AL el llamado
- "δerrorlevelπ" que devuelve el programa (en este caso 00h). Si volvemos a te-
- clear el comando 'p' saldremos al DOS ya que se ejecutará la interrución (como
- podemos ver en el campo "siguiente instrucción").
- Tal vez, si escribes 'Ωu 100π' para ver el programa que has hecho, te sorprenda
- ver que la cadena 'hola mundo!!$' no aparece por ningún sítio. Ello es porque
- cuando le dices al debug que desensamble un trozo de código, interpreta que
- ese trozo es de intrucciones y por ello trata de dar un significado a la ristra
- de bytes que componen la cadena. Para ver las cadenas que definamos en nuestro
- programa, deberemos escribir la instrucción 'd 100' que significa "dump"
- (volcar) y que hace un caracteristico listado hexadecimal.
- Si todavia no te has dado cuenta de que significado tiene ese 100 que ponemos
- detrás de las instrucciones 'd' y 'u', deberias de haberte dado cuenta de que
- se trata de la dirección (sólo offset, se supone que el segmento es CS) donde
- el debug debe empezar a volcar o desensamblar. Opcionalmente tambien puedes
- poner detrás otro offset para decirle cuando debe acabar, aunque si no lo
- haces no pasa nada ya que entiende que quieres un trozito.
- La instrucción 'w' (write) sirve para almacenar en disco el programa (re-
- cuerda que ya la has usado para salvar el programa) desde la posición CS:0100h
- hasta el offset dentro de CS almacenado en CX más 100h. En realidad en CX lo
- que debes guardar es la longitud del programa (en el "hola mundo" era 1Dh
- porque despues de introducir la última instrucción [la del mensaje] apareció
- como siguiente dirección para ensamblar la δXXXX:01D1hπ; vuelve a reescribir el
- programa para observarlo). Para poner un valor en un registro, debemos usar
- la instrucción 'r' que usado sin parámetros devuelve el valor de los registros
- y con un registro como parámetro permite su modificación ('rcx' en este caso).
- Otra instrucción interesante es 'δgπ' (go) que permitirá que un programa que
- estamos trazando con 't' o 'p' pueda ser ejecutado hasta su finalización. Si
- entramos al programa sin especificar un nombre para el archivo que queremos
- almacenar en disco o queremos poner otro usaremos la intrucción 'n' pasandole
- como parámetro el nombre del fichero de salida con extensión COM (no se pueden
- hacer EXEs con el debug).
-
- εInstrucciones aritméticasπ
-
- Las instrucciones aritméticas son las que permiten operar con los registros
- y hacer sencillos cálculo en complemento a dos (ver anteriores artículos).
- Podemos sumar, restar, multiplicar y dividir directamente y podemos realizar
- operaciones más complicadas a base de estas más simples. Los prototipos de las
- instrucciones son estos:
-
- Γ ADD <destino>, <origen>π
- Γ SUB <destino>, <origen>π
- Γ MUL <multipl>π
- Γ IMUL <multipl>π
- Γ DIV <divisor>π
- Γ IDIV <divisor>π
-
- Lo primero que se observa es que hay 2 instrucciones para multiplicar y 2
- más para dividir. Las instrucciones δMULπ y δDIVπ consideran números sin signo,
- mientras δIMULπ e δIDIVπ los consideran con signo. Para sumar 2 registros lo que
- hacemos es poner el destino delante y lo que le sumaremos detrás. Es equiva-
- lente a la instrucción de C "+=". Veamos un par de ejemplos:
-
- Γ MOV AX, 25π φ ; AX pasa a valer 25π
- Γ MOV BX, 35π φ ; BX pasa a valer 35π
- Γ ADD AX, BXπ φ ; BX se mantiene y AX valdrá 25+35=60π
- Γπ
- Γ MOV AX, 100π φ ; AX = 100π
- Γ MOV BX, 50 π φ ; BX = 50π
- Γ ····· π φ ; Queremos que AX y BX no cambien, usamosπ
- Γ MOV CX, AX π φ ; otro registro para almacenar el resultado.π
- Γ ADD CX, BX π φ ; CX = 150, AX = 100, BX = 50π
-
- Tambien podemos operar con memoria, pero recordando siempre que el 80x86
- no nos permite operaciones de memoria a memoria (es incorrecto MOV [v1], [v2]).
- Para la resta lo mismo, se restará el "origen" al "destino", es decir, como
- si hiciesemos en un HLL "destino=destino-origen". Algunos ejemplos:
-
- Γ MOV AX, 100 φ; AX vale 100π
- Γ SUB AX, 10 φ; AX valdrá 90π
- Γ MOV BX, 90 φ; BX = 90π
- Γ SUB AX, BX φ; AX se hace 0π
-
- Γ MOV AX, 25 φ; AX = 25π
- Γ MOV BX, 15 φ; BX = 15π
- Γ ····· φ; Supongamos que queremos guardar en memoriaπ
- Γ MOV DS:[130h], AX φ; AX - BXπ
- Γ SUB DS:[130h], BX φ; En memoria 10π
-
- Despues de ver este último ejemplo tengo que aclarar un par de cosas. Lo
- primero, cuando no escriba una "h" o una "o" al final del número, estaré
- escribiendo los números en decimal (a no ser que el código este diseñado
- explícitamente para ser usado en el debug). Lo segundo, que cuando queramos
- almacenar en memoria un valor, deberemos haber definido una zona para el
- almacenamiento de memoria (con las instrucciones δDBπ, δDWπ o δDDπ que hemos visto
- superficialmente) y luego recordar la dirección donde están definidas para
- acceder a ellas escribiendo su dirección entre corchetes. Este sistema tan
- arcaico es sólo necesario con el debug, ya que todos los ensambladores
- modernos incorporan etiquetas (en el próximo número). Por ejemplo, tomemos
- del debug un trozo de código que haga uso de esta caracteristica:
-
- Γ10BB:0100 8CC8 MOV AX,CS φ ; Primero metemos el segmentoπ
- Γ10BB:0102 8ED8 MOV DS,AX φ ; correcto. π
- Γ10BB:0104 B85000 MOV AX,0050π π
- Γ10BB:0107 BB3000 MOV BX,0030 φ ; Los datos a restar. π
- Γ10BB:010A A31601 MOV [0116],AXφ ; Escribimos a memoria. π
- Γ10BB:010D 291E1601 SUB [0116],BXφ ; Restamos de memoria. π
- Γ10BB:0111 B8004C MOV AX,4C00 φ ; Función de salir al DOS π
- Γ10BB:0114 CD21 INT 21 φ ; Hasta luego lucas! π
- Γ10BB:0116 0000 DW 0 φ ; Aquí es donde definimos la WORD queπ
- φ ; almacenará los numeritos. π
-
- Las instrucciones multiplicativas tienen un aspecto más complicado, sólo
- tienen 1 parámetro. Para hacer una multiplicación se coge el acumulador y se
- opera con el registro o posición de memoria que le pasemos como parámetro.
- Pero, ¿que es eso del acumulador? Pues depende, puede ser δALπ o δAXπ. Como sabrás
- (si prestaste atención en los anteriores números) cuando multiplicamos dos
- números de n-bits, la mayor de las cifrás que tendremos será de 2n-bits. Así,
- pues, se estandariza la operación de multiplicación y si se quiere multiplicar
- 2 números deberán tener los mismos bits (8 o 16) y el resultado se almacenará
- en un registro de 2n-bits. En definitiva, si queremos multiplicar un número de
- 8-bits el acumulador será δALπ y si es de 16 será δAXπ. Para almacenar el resultado
- de los de 8-bits usaremos AX y para los de 16 usaremos el par δDX-AXπ donde DX
- serán los 16-bits de mayor peso y AX los 16 de menor. Ejemplos:
-
- Γ MOV AX, 10 φ ; AX = 10π
- Γ MOV BX, 50 φ ; BX = 50 π
- Γ MUL BX φ ; DX-AX = AX * BX, y como 500 cabe en 16 bits π
- φ ; AX valdrá 500 y DX será 0. π
-
- Γ MOV AL, 120 φ ; AL = 120π
- Γ MOV CL, 40 φ ; CL = 40 π
- Γ MUL CL φ ; AX = AL * CL = 120 * 40 = 4800π
-
- La división es lo contrario de la multiplicación, cuando uso el divisor de
- 8-bits, el acumulador es de 16 y cuando lo uso de 16-bits, el acumulador será
- el de 32 (DX-AX). Vamos directamente con un ejemplo:
-
- Γ MOV CL, 100 φ ; CL = 100π
- Γ MOV BL, 10 φ ; BL = 10. Queremos dividir CL/BL:π
- Γ MOV AL, CL φ ; Tenemos un problema, AH puede contener unπ
- Γ MOV AH, 0 φ ; valor distinto de 0; pues lo "borramos".π
- Γ DIV BL φ ; AL = 100 / 10 = 10 π
-
- El cociente de esta operación de división se almacena en AL y el resto lo
- hace en δAHπ. Como el acumulador para divisores de 8-bits es de 16, hemos copiado
- a δAXπ el dividendo, haciendo 0 la parte de arriba (más adelante veremos como
- hacer esto en una sóla instrucción). Si usasemos un registro de 16-bits
- tendriamos que hacer δDX=0π.
-
- Creo que ya ha habido bastante por hoy, ¿no? En el próximo número veremos
- las instrucciones lógicas, los saltos, comparaciones y si hay tiempo las
- entradas y salidas, además de un especial interrupciones. Os dejo unos deberes
- para casa: determinad que valores contendrán los registros de uso general
- (δAXπ, δBXπ, δCXπ y δDXπ) al terminar el programa o decid si no es posible ensamblarlos
- por contener algún error (se deben hacer sin usar el PC):
-
- Γ MOV AX, 100 MOV AX, 30 MOV AL, 500π
- Γ MOV BX, 200 MOV BX, 10 MOV BL, 100 π
- Γ ADD AX, BX MUL BX SUB AL, BL π
- Γ MUL BX DIV BX MUL 10π
- Γ ADD AL, BXπ
-
-
-
-
-
-
-